时间同步重试

chengzhenyu 8 lat temu
rodzic
commit
6bdd4b66ef

+ 33 - 2
app/src/main/java/ai/pai/lensman/main/MainActivity.java

@@ -7,12 +7,15 @@ import android.support.v7.widget.GridLayoutManager;
7 7
 import android.support.v7.widget.LinearLayoutManager;
8 8
 import android.support.v7.widget.RecyclerView;
9 9
 import android.view.View;
10
+import android.widget.Button;
10 11
 import android.widget.ImageView;
12
+import android.widget.RelativeLayout;
11 13
 import android.widget.TextView;
12 14
 import android.widget.Toast;
13 15
 
14 16
 import com.android.common.utils.LogHelper;
15 17
 import com.android.common.utils.NetworkUtil;
18
+import com.android.views.loadingdrawable.LoadingView;
16 19
 import com.umeng.analytics.MobclickAgent;
17 20
 
18 21
 import java.util.ArrayList;
@@ -36,6 +39,10 @@ public class MainActivity extends BaseActivity implements MainContract.View {
36 39
     @BindView(R.id.iv_add_session) ImageView addSessionBtn;
37 40
     @BindView(R.id.container_view) View containerView;
38 41
     @BindView(R.id.recycler_view_sessions) RecyclerView sessionsRecyclerView;
42
+    @BindView(R.id.sync_time_view)   RelativeLayout syncTimeView;
43
+    @BindView(R.id.loading_gear_view) LoadingView loadingView;
44
+    @BindView(R.id.tv_sync_time_status) TextView syncStatusTextView;
45
+    @BindView(R.id.btn_retry_sync) Button retrySyncBtn;
39 46
     private SessionRecyclerAdapter adapter;
40 47
     private MainContract.Presenter presenter;
41 48
     private long exitTime;
@@ -77,6 +84,12 @@ public class MainActivity extends BaseActivity implements MainContract.View {
77 84
         }
78 85
     }
79 86
 
87
+    @OnClick(R.id.btn_retry_sync)
88
+    void retrySync(){
89
+        showTimeSyncView();
90
+        presenter.resyncTime();
91
+    }
92
+
80 93
     @OnClick(R.id.layout_brief)
81 94
     void jumpToBriefs(){
82 95
         MobclickAgent.onEvent(this, UmengEvent.home_brief_btn_click);
@@ -120,8 +133,9 @@ public class MainActivity extends BaseActivity implements MainContract.View {
120 133
 
121 134
     @Override
122 135
     public void showSessionViews() {
123
-        noDataLayout.setVisibility(android.view.View.GONE);
124
-        sessionsRecyclerView.setVisibility(android.view.View.VISIBLE);
136
+        noDataLayout.setVisibility(View.GONE);
137
+        syncTimeView.setVisibility(View.GONE);
138
+        sessionsRecyclerView.setVisibility(View.VISIBLE);
125 139
     }
126 140
 
127 141
     @Override
@@ -140,6 +154,23 @@ public class MainActivity extends BaseActivity implements MainContract.View {
140 154
         addSessionBtn.setEnabled(isEnabled);
141 155
     }
142 156
 
157
+    @Override
158
+    public void showTimeSyncView() {
159
+        sessionsRecyclerView.setVisibility(View.GONE);
160
+        syncTimeView.setVisibility(View.VISIBLE);
161
+        loadingView.setVisibility(View.VISIBLE);
162
+        syncStatusTextView.setText(R.string.time_syncing);
163
+    }
164
+
165
+    @Override
166
+    public void showRetrySyncView(int strId) {
167
+        retrySyncBtn.setVisibility(View.VISIBLE);
168
+        loadingView.setVisibility(View.INVISIBLE);
169
+        if(strId!=0){
170
+            syncStatusTextView.setText(strId);
171
+        }
172
+
173
+    }
143 174
 
144 175
     private void jumpToSelectedSession(SessionBean sessionBean) {
145 176
         Intent intent = new Intent(this, SessionActivity.class);

+ 3 - 1
app/src/main/java/ai/pai/lensman/main/MainContract.java

@@ -18,11 +18,13 @@ public class MainContract {
18 18
         void updateSessionUploadViewAt(int position);
19 19
         void refreshSessionViews(ArrayList<SessionBean> sessionList);
20 20
         void setNewSessionBtnEnabled(boolean isEnabled);
21
+        void showTimeSyncView();
22
+        void showRetrySyncView(int strId);
21 23
     }
22 24
 
23 25
     interface Presenter extends BasePresenter{
24 26
         SessionBean createNewSession();
25
-
27
+        void resyncTime();
26 28
     }
27 29
 
28 30
 }

+ 11 - 1
app/src/main/java/ai/pai/lensman/main/MainPresenter.java

@@ -103,6 +103,7 @@ class MainPresenter implements MainContract.Presenter,BaseInteractor.InteractorL
103 103
             view.showBoxDisconnectedView();
104 104
         }
105 105
         view.setNewSessionBtnEnabled(false);
106
+        view.showTimeSyncView();
106 107
         if(sessionIds==null|| sessionIds.size()<20){
107 108
             fetchSessionIdsInteractor.startJob();
108 109
         }
@@ -136,6 +137,7 @@ class MainPresenter implements MainContract.Presenter,BaseInteractor.InteractorL
136 137
         if(sessionIds==null|sessionIds.size()<20){
137 138
             return;
138 139
         }
140
+
139 141
         view.setNewSessionBtnEnabled(true);
140 142
         if(sessionList.size()==0){
141 143
             view.showEmptyView();
@@ -169,6 +171,14 @@ class MainPresenter implements MainContract.Presenter,BaseInteractor.InteractorL
169 171
         return sessionBean;
170 172
     }
171 173
 
174
+    @Override
175
+    public void resyncTime() {
176
+        if(syncTimeInteractor!=null){
177
+            syncTimeInteractor.cancelJob();
178
+            syncTimeInteractor.startJob();
179
+        }
180
+    }
181
+
172 182
     private long getSessionDateInLongFormat(){
173 183
         SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
174 184
         String dateStr = format.format(new Date());
@@ -274,7 +284,7 @@ class MainPresenter implements MainContract.Presenter,BaseInteractor.InteractorL
274 284
             onDataReady();
275 285
         }else{
276 286
             if(strId!=0){
277
-                view.showSnackBar(strId);
287
+                view.showRetrySyncView(strId);
278 288
             }
279 289
         }
280 290
     }

+ 12 - 0
app/src/main/res/drawable/retry_sync_btn_rounded_rect_bg.xml

@@ -0,0 +1,12 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
3
+    android:shape="rectangle" >
4
+
5
+    <solid android:color="@color/transparent" />
6
+
7
+    <corners
8
+        android:radius="6dp"/>
9
+
10
+    <stroke android:width="1dp" android:color="@color/white"/>
11
+
12
+</shape>

+ 41 - 0
app/src/main/res/layout/activity_main.xml

@@ -1,5 +1,6 @@
1 1
 <?xml version="1.0" encoding="utf-8"?>
2 2
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3
+    xmlns:app="http://schemas.android.com/apk/res-auto"
3 4
     android:id="@+id/container_view"
4 5
     android:layout_width="match_parent"
5 6
     android:layout_height="match_parent"
@@ -54,6 +55,46 @@
54 55
         android:layout_centerInParent="true"
55 56
         android:src="@drawable/no_photo_tip" />
56 57
 
58
+    <RelativeLayout
59
+        android:id="@+id/sync_time_view"
60
+        android:layout_width="match_parent"
61
+        android:layout_height="match_parent"
62
+        android:background="@color/half_transparent">
63
+
64
+        <com.android.views.loadingdrawable.LoadingView
65
+            android:id="@+id/loading_gear_view"
66
+            android:layout_width="180dp"
67
+            android:layout_height="180dp"
68
+            android:layout_centerInParent="true"
69
+            app:loading_renderer="GearLoadingRenderer"/>
70
+
71
+        <TextView
72
+            android:id="@+id/tv_sync_time_status"
73
+            android:layout_width="wrap_content"
74
+            android:layout_height="wrap_content"
75
+            android:textSize="16sp"
76
+            android:text="@string/time_syncing"
77
+            android:layout_below="@id/loading_gear_view"
78
+            android:layout_marginTop="10dp"
79
+            android:layout_centerHorizontal="true"
80
+            android:textColor="@color/white"/>
81
+
82
+        <Button
83
+            android:id="@+id/btn_retry_sync"
84
+            android:layout_width="140dp"
85
+            android:layout_height="40dp"
86
+            android:layout_centerHorizontal="true"
87
+            android:layout_below="@id/tv_sync_time_status"
88
+            android:layout_marginTop="10dp"
89
+            android:background="@drawable/retry_sync_btn_rounded_rect_bg"
90
+            android:gravity="center"
91
+            android:text="@string/time_sync_retry"
92
+            android:textColor="@color/white"
93
+            android:visibility="gone"
94
+            android:textSize="18sp" />
95
+    </RelativeLayout>
96
+
97
+
57 98
     <android.support.v7.widget.RecyclerView
58 99
         android:id="@+id/recycler_view_sessions"
59 100
         android:layout_width="match_parent"

+ 6 - 2
app/src/main/res/values/strings.xml

@@ -174,7 +174,11 @@
174 174
 
175 175
     <string name="upload_settings">上传管理</string>
176 176
 
177
-    <string name="sync_time_server_error">时间同步出错,请检查网络连接后退出重试</string>
178
-    <string name="sync_time_box_error">时间同步出错,请检查盒子是否打开后退出重试</string>
177
+    <string name="sync_time_server_error">时间同步出错,请检查网络连接</string>
178
+    <string name="sync_time_box_error">时间同步出错,请检查盒子是否打开</string>
179
+
180
+    <string name="time_syncing">正在同步时间...</string>
181
+
182
+    <string name="time_sync_retry">重新同步</string>
179 183
 
180 184
 </resources>

+ 11 - 0
views/src/main/java/com/android/views/loadingdrawable/DensityUtil.java

@@ -0,0 +1,11 @@
1
+package com.android.views.loadingdrawable;
2
+
3
+import android.content.Context;
4
+
5
+public class DensityUtil {
6
+
7
+    public static float dip2px(Context context, float dpValue) {
8
+        float scale = context.getResources().getDisplayMetrics().density;
9
+        return dpValue * scale;
10
+    }  
11
+}

+ 78 - 0
views/src/main/java/com/android/views/loadingdrawable/LoadingView.java

@@ -0,0 +1,78 @@
1
+package com.android.views.loadingdrawable;
2
+
3
+import android.content.Context;
4
+import android.content.res.TypedArray;
5
+import android.util.AttributeSet;
6
+import android.view.View;
7
+import android.widget.ImageView;
8
+
9
+import com.android.views.R;
10
+import com.android.views.loadingdrawable.render.LoadingDrawable;
11
+import com.android.views.loadingdrawable.render.LoadingRenderer;
12
+import com.android.views.loadingdrawable.render.LoadingRendererFactory;
13
+
14
+public class LoadingView extends ImageView {
15
+    private LoadingDrawable mLoadingDrawable;
16
+
17
+    public LoadingView(Context context) {
18
+        super(context);
19
+    }
20
+
21
+    public LoadingView(Context context, AttributeSet attrs) {
22
+        super(context, attrs);
23
+        initAttrs(context, attrs);
24
+    }
25
+
26
+    private void initAttrs(Context context, AttributeSet attrs) {
27
+        try {
28
+            TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LoadingView);
29
+            int loadingRendererId = ta.getInt(R.styleable.LoadingView_loading_renderer, 0);
30
+            LoadingRenderer loadingRenderer = LoadingRendererFactory.createLoadingRenderer(context, loadingRendererId);
31
+            setLoadingRenderer(loadingRenderer);
32
+            ta.recycle();
33
+        } catch (Exception e) {
34
+            e.printStackTrace();
35
+        }
36
+    }
37
+
38
+    public void setLoadingRenderer(LoadingRenderer loadingRenderer) {
39
+        mLoadingDrawable = new LoadingDrawable(loadingRenderer);
40
+        setImageDrawable(mLoadingDrawable);
41
+    }
42
+
43
+    @Override
44
+    protected void onAttachedToWindow() {
45
+        super.onAttachedToWindow();
46
+        startAnimation();
47
+    }
48
+
49
+    @Override
50
+    protected void onDetachedFromWindow() {
51
+        super.onDetachedFromWindow();
52
+        stopAnimation();
53
+    }
54
+
55
+    @Override
56
+    protected void onVisibilityChanged(View changedView, int visibility) {
57
+        super.onVisibilityChanged(changedView, visibility);
58
+
59
+        final boolean visible = visibility == VISIBLE && getVisibility() == VISIBLE;
60
+        if (visible) {
61
+            startAnimation();
62
+        } else {
63
+            stopAnimation();
64
+        }
65
+    }
66
+
67
+    private void startAnimation() {
68
+        if (mLoadingDrawable != null) {
69
+            mLoadingDrawable.start();
70
+        }
71
+    }
72
+
73
+    private void stopAnimation() {
74
+        if (mLoadingDrawable != null) {
75
+            mLoadingDrawable.stop();
76
+        }
77
+    }
78
+}

+ 87 - 0
views/src/main/java/com/android/views/loadingdrawable/render/LoadingDrawable.java

@@ -0,0 +1,87 @@
1
+package com.android.views.loadingdrawable.render;
2
+
3
+import android.graphics.Canvas;
4
+import android.graphics.ColorFilter;
5
+import android.graphics.PixelFormat;
6
+import android.graphics.Rect;
7
+import android.graphics.drawable.Animatable;
8
+import android.graphics.drawable.Drawable;
9
+
10
+public class LoadingDrawable extends Drawable implements Animatable {
11
+    private final LoadingRenderer mLoadingRender;
12
+
13
+    private final Callback mCallback = new Callback() {
14
+        @Override
15
+        public void invalidateDrawable(Drawable d) {
16
+            invalidateSelf();
17
+        }
18
+
19
+        @Override
20
+        public void scheduleDrawable(Drawable d, Runnable what, long when) {
21
+            scheduleSelf(what, when);
22
+        }
23
+
24
+        @Override
25
+        public void unscheduleDrawable(Drawable d, Runnable what) {
26
+            unscheduleSelf(what);
27
+        }
28
+    };
29
+
30
+    public LoadingDrawable(LoadingRenderer loadingRender) {
31
+        this.mLoadingRender = loadingRender;
32
+        this.mLoadingRender.setCallback(mCallback);
33
+    }
34
+
35
+    @Override
36
+    protected void onBoundsChange(Rect bounds) {
37
+        super.onBoundsChange(bounds);
38
+        this.mLoadingRender.setBounds(bounds);
39
+    }
40
+
41
+    @Override
42
+    public void draw(Canvas canvas) {
43
+        if (!getBounds().isEmpty()) {
44
+            this.mLoadingRender.draw(canvas);
45
+        }
46
+    }
47
+
48
+    @Override
49
+    public void setAlpha(int alpha) {
50
+        this.mLoadingRender.setAlpha(alpha);
51
+    }
52
+
53
+    @Override
54
+    public void setColorFilter(ColorFilter cf) {
55
+        this.mLoadingRender.setColorFilter(cf);
56
+    }
57
+
58
+    @Override
59
+    public int getOpacity() {
60
+        return PixelFormat.TRANSLUCENT;
61
+    }
62
+
63
+    @Override
64
+    public void start() {
65
+        this.mLoadingRender.start();
66
+    }
67
+
68
+    @Override
69
+    public void stop() {
70
+        this.mLoadingRender.stop();
71
+    }
72
+
73
+    @Override
74
+    public boolean isRunning() {
75
+        return this.mLoadingRender.isRunning();
76
+    }
77
+
78
+    @Override
79
+    public int getIntrinsicHeight() {
80
+        return (int) this.mLoadingRender.mHeight;
81
+    }
82
+
83
+    @Override
84
+    public int getIntrinsicWidth() {
85
+        return (int) this.mLoadingRender.mWidth;
86
+    }
87
+}

+ 120 - 0
views/src/main/java/com/android/views/loadingdrawable/render/LoadingRenderer.java

@@ -0,0 +1,120 @@
1
+package com.android.views.loadingdrawable.render;
2
+
3
+import android.animation.Animator;
4
+import android.animation.ValueAnimator;
5
+import android.content.Context;
6
+import android.graphics.Canvas;
7
+import android.graphics.ColorFilter;
8
+import android.graphics.Rect;
9
+import android.graphics.drawable.Drawable;
10
+import android.view.animation.Animation;
11
+import android.view.animation.LinearInterpolator;
12
+
13
+import com.android.views.loadingdrawable.DensityUtil;
14
+
15
+public abstract class LoadingRenderer {
16
+    private static final long ANIMATION_DURATION = 1333;
17
+    private static final float DEFAULT_SIZE = 56.0f;
18
+
19
+    private final ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener
20
+            = new ValueAnimator.AnimatorUpdateListener() {
21
+        @Override
22
+        public void onAnimationUpdate(ValueAnimator animation) {
23
+            computeRender((float) animation.getAnimatedValue());
24
+            invalidateSelf();
25
+        }
26
+    };
27
+
28
+    /**
29
+     * Whenever {@link LoadingDrawable} boundary changes mBounds will be updated.
30
+     * More details you can see {@link LoadingDrawable#onBoundsChange(Rect)}
31
+     */
32
+    protected final Rect mBounds = new Rect();
33
+
34
+    private Drawable.Callback mCallback;
35
+    private ValueAnimator mRenderAnimator;
36
+
37
+    protected long mDuration;
38
+
39
+    protected float mWidth;
40
+    protected float mHeight;
41
+
42
+    public LoadingRenderer(Context context) {
43
+        initParams(context);
44
+        setupAnimators();
45
+    }
46
+
47
+    @Deprecated
48
+    protected void draw(Canvas canvas, Rect bounds) {
49
+    }
50
+
51
+    protected void draw(Canvas canvas) {
52
+        draw(canvas, mBounds);
53
+    }
54
+
55
+    protected abstract void computeRender(float renderProgress);
56
+
57
+    protected abstract void setAlpha(int alpha);
58
+
59
+    protected abstract void setColorFilter(ColorFilter cf);
60
+
61
+    protected abstract void reset();
62
+
63
+    protected void addRenderListener(Animator.AnimatorListener animatorListener) {
64
+        mRenderAnimator.addListener(animatorListener);
65
+    }
66
+
67
+    void start() {
68
+        reset();
69
+        mRenderAnimator.addUpdateListener(mAnimatorUpdateListener);
70
+
71
+        mRenderAnimator.setRepeatCount(ValueAnimator.INFINITE);
72
+        mRenderAnimator.setDuration(mDuration);
73
+        mRenderAnimator.start();
74
+    }
75
+
76
+    void stop() {
77
+        // if I just call mRenderAnimator.end(),
78
+        // it will always call the method onAnimationUpdate(ValueAnimator animation)
79
+        // why ? if you know why please send email to me (dinus_developer@163.com)
80
+        mRenderAnimator.removeUpdateListener(mAnimatorUpdateListener);
81
+
82
+        mRenderAnimator.setRepeatCount(0);
83
+        mRenderAnimator.setDuration(0);
84
+        mRenderAnimator.end();
85
+    }
86
+
87
+    boolean isRunning() {
88
+        return mRenderAnimator.isRunning();
89
+    }
90
+
91
+    void setCallback(Drawable.Callback callback) {
92
+        this.mCallback = callback;
93
+    }
94
+
95
+    void setBounds(Rect bounds) {
96
+        mBounds.set(bounds);
97
+    }
98
+
99
+    private void initParams(Context context) {
100
+        mWidth = DensityUtil.dip2px(context, DEFAULT_SIZE);
101
+        mHeight = DensityUtil.dip2px(context, DEFAULT_SIZE);
102
+
103
+        mDuration = ANIMATION_DURATION;
104
+    }
105
+
106
+    private void setupAnimators() {
107
+        mRenderAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
108
+        mRenderAnimator.setRepeatCount(Animation.INFINITE);
109
+        mRenderAnimator.setRepeatMode(Animation.RESTART);
110
+        mRenderAnimator.setDuration(mDuration);
111
+        //fuck you! the default interpolator is AccelerateDecelerateInterpolator
112
+        mRenderAnimator.setInterpolator(new LinearInterpolator());
113
+        mRenderAnimator.addUpdateListener(mAnimatorUpdateListener);
114
+    }
115
+
116
+    private void invalidateSelf() {
117
+        mCallback.invalidateDrawable(null);
118
+    }
119
+
120
+}

+ 36 - 0
views/src/main/java/com/android/views/loadingdrawable/render/LoadingRendererFactory.java

@@ -0,0 +1,36 @@
1
+package com.android.views.loadingdrawable.render;
2
+
3
+import android.content.Context;
4
+import android.util.SparseArray;
5
+
6
+import com.android.views.loadingdrawable.render.circle.rotate.GearLoadingRenderer;
7
+
8
+import java.lang.reflect.Constructor;
9
+
10
+public final class LoadingRendererFactory {
11
+    private static final SparseArray<Class<? extends LoadingRenderer>> LOADING_RENDERERS = new SparseArray<>();
12
+
13
+    static {
14
+        LOADING_RENDERERS.put(3, GearLoadingRenderer.class);
15
+
16
+    }
17
+
18
+    private LoadingRendererFactory() {
19
+    }
20
+
21
+    public static LoadingRenderer createLoadingRenderer(Context context, int loadingRendererId) throws Exception {
22
+        Class<?> loadingRendererClazz = LOADING_RENDERERS.get(loadingRendererId);
23
+        Constructor<?>[] constructors = loadingRendererClazz.getDeclaredConstructors();
24
+        for (Constructor<?> constructor : constructors) {
25
+            Class<?>[] parameterTypes = constructor.getParameterTypes();
26
+            if (parameterTypes != null
27
+                    && parameterTypes.length == 1
28
+                    && parameterTypes[0].equals(Context.class)) {
29
+                constructor.setAccessible(true);
30
+                return (LoadingRenderer) constructor.newInstance(context);
31
+            }
32
+        }
33
+
34
+        throw new InstantiationException();
35
+    }
36
+}

+ 287 - 0
views/src/main/java/com/android/views/loadingdrawable/render/circle/rotate/GearLoadingRenderer.java

@@ -0,0 +1,287 @@
1
+package com.android.views.loadingdrawable.render.circle.rotate;
2
+
3
+import android.animation.Animator;
4
+import android.animation.AnimatorListenerAdapter;
5
+import android.content.Context;
6
+import android.graphics.Canvas;
7
+import android.graphics.Color;
8
+import android.graphics.ColorFilter;
9
+import android.graphics.Paint;
10
+import android.graphics.RectF;
11
+import android.support.annotation.IntRange;
12
+import android.view.animation.AccelerateInterpolator;
13
+import android.view.animation.DecelerateInterpolator;
14
+import android.view.animation.Interpolator;
15
+
16
+import com.android.views.loadingdrawable.DensityUtil;
17
+import com.android.views.loadingdrawable.render.LoadingRenderer;
18
+
19
+public class GearLoadingRenderer extends LoadingRenderer {
20
+    private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();
21
+    private static final Interpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
22
+
23
+    private static final int GEAR_COUNT = 4;
24
+    private static final int NUM_POINTS = 3;
25
+    private static final int MAX_ALPHA = 255;
26
+    private static final int DEGREE_360 = 360;
27
+
28
+    private static final int DEFAULT_GEAR_SWIPE_DEGREES = 60;
29
+
30
+    private static final float FULL_GROUP_ROTATION = 3.0f * DEGREE_360;
31
+
32
+    private static final float START_SCALE_DURATION_OFFSET = 0.3f;
33
+    private static final float START_TRIM_DURATION_OFFSET = 0.5f;
34
+    private static final float END_TRIM_DURATION_OFFSET = 0.7f;
35
+    private static final float END_SCALE_DURATION_OFFSET = 1.0f;
36
+
37
+    private static final float DEFAULT_CENTER_RADIUS = 12.5f;
38
+    private static final float DEFAULT_STROKE_WIDTH = 2.5f;
39
+
40
+    private static final int DEFAULT_COLOR = Color.WHITE;
41
+
42
+    private final Paint mPaint = new Paint();
43
+    private final RectF mTempBounds = new RectF();
44
+
45
+    private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() {
46
+        @Override
47
+        public void onAnimationRepeat(Animator animator) {
48
+            super.onAnimationRepeat(animator);
49
+            storeOriginals();
50
+
51
+            mStartDegrees = mEndDegrees;
52
+            mRotationCount = (mRotationCount + 1) % NUM_POINTS;
53
+        }
54
+
55
+        @Override
56
+        public void onAnimationStart(Animator animation) {
57
+            super.onAnimationStart(animation);
58
+            mRotationCount = 0;
59
+        }
60
+    };
61
+
62
+    private int mColor;
63
+
64
+    private int mGearCount;
65
+    private int mGearSwipeDegrees;
66
+
67
+    private float mStrokeInset;
68
+
69
+    private float mRotationCount;
70
+    private float mGroupRotation;
71
+
72
+    private float mScale;
73
+    private float mEndDegrees;
74
+    private float mStartDegrees;
75
+    private float mSwipeDegrees;
76
+    private float mOriginEndDegrees;
77
+    private float mOriginStartDegrees;
78
+
79
+    private float mStrokeWidth;
80
+    private float mCenterRadius;
81
+
82
+    private GearLoadingRenderer(Context context) {
83
+        super(context);
84
+
85
+        init(context);
86
+        setupPaint();
87
+        addRenderListener(mAnimatorListener);
88
+    }
89
+
90
+    private void init(Context context) {
91
+        mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH);
92
+        mCenterRadius = DensityUtil.dip2px(context, DEFAULT_CENTER_RADIUS);
93
+
94
+        mColor = DEFAULT_COLOR;
95
+
96
+        mGearCount = GEAR_COUNT;
97
+        mGearSwipeDegrees = DEFAULT_GEAR_SWIPE_DEGREES;
98
+    }
99
+
100
+    private void setupPaint() {
101
+        mPaint.setAntiAlias(true);
102
+        mPaint.setStrokeWidth(mStrokeWidth);
103
+        mPaint.setStyle(Paint.Style.STROKE);
104
+        mPaint.setStrokeCap(Paint.Cap.ROUND);
105
+
106
+        initStrokeInset(mWidth, mHeight);
107
+    }
108
+
109
+    @Override
110
+    protected void draw(Canvas canvas) {
111
+        int saveCount = canvas.save();
112
+
113
+        mTempBounds.set(mBounds);
114
+        mTempBounds.inset(mStrokeInset, mStrokeInset);
115
+        mTempBounds.inset(mTempBounds.width() * (1.0f - mScale) / 2.0f, mTempBounds.width() * (1.0f - mScale) / 2.0f);
116
+
117
+        canvas.rotate(mGroupRotation, mTempBounds.centerX(), mTempBounds.centerY());
118
+
119
+        mPaint.setColor(mColor);
120
+        mPaint.setAlpha((int) (MAX_ALPHA * mScale));
121
+        mPaint.setStrokeWidth(mStrokeWidth * mScale);
122
+
123
+        if (mSwipeDegrees != 0) {
124
+            for (int i = 0; i < mGearCount; i++) {
125
+                canvas.drawArc(mTempBounds, mStartDegrees + DEGREE_360 / mGearCount * i, mSwipeDegrees, false, mPaint);
126
+            }
127
+        }
128
+
129
+        canvas.restoreToCount(saveCount);
130
+    }
131
+
132
+    @Override
133
+    protected void computeRender(float renderProgress) {
134
+        // Scaling up the start size only occurs in the first 20% of a single ring animation
135
+        if (renderProgress <= START_SCALE_DURATION_OFFSET) {
136
+            float startScaleProgress = (renderProgress) / START_SCALE_DURATION_OFFSET;
137
+            mScale = DECELERATE_INTERPOLATOR.getInterpolation(startScaleProgress);
138
+        }
139
+
140
+        // Moving the start trim only occurs between 20% to 50% of a single ring animation
141
+        if (renderProgress <= START_TRIM_DURATION_OFFSET && renderProgress > START_SCALE_DURATION_OFFSET) {
142
+            float startTrimProgress = (renderProgress - START_SCALE_DURATION_OFFSET) / (START_TRIM_DURATION_OFFSET - START_SCALE_DURATION_OFFSET);
143
+            mStartDegrees = mOriginStartDegrees + mGearSwipeDegrees * startTrimProgress;
144
+        }
145
+
146
+        // Moving the end trim starts between 50% to 80% of a single ring animation
147
+        if (renderProgress <= END_TRIM_DURATION_OFFSET && renderProgress > START_TRIM_DURATION_OFFSET) {
148
+            float endTrimProgress = (renderProgress - START_TRIM_DURATION_OFFSET) / (END_TRIM_DURATION_OFFSET - START_TRIM_DURATION_OFFSET);
149
+            mEndDegrees = mOriginEndDegrees + mGearSwipeDegrees * endTrimProgress;
150
+        }
151
+
152
+        // Scaling down the end size starts after 80% of a single ring animation
153
+        if (renderProgress > END_TRIM_DURATION_OFFSET) {
154
+            float endScaleProgress = (renderProgress - END_TRIM_DURATION_OFFSET) / (END_SCALE_DURATION_OFFSET - END_TRIM_DURATION_OFFSET);
155
+            mScale = 1.0f - ACCELERATE_INTERPOLATOR.getInterpolation(endScaleProgress);
156
+        }
157
+
158
+        if (renderProgress <= END_TRIM_DURATION_OFFSET && renderProgress > START_SCALE_DURATION_OFFSET) {
159
+            float rotateProgress = (renderProgress - START_SCALE_DURATION_OFFSET) / (END_TRIM_DURATION_OFFSET - START_SCALE_DURATION_OFFSET);
160
+            mGroupRotation = ((FULL_GROUP_ROTATION / NUM_POINTS) * rotateProgress) + (FULL_GROUP_ROTATION * (mRotationCount / NUM_POINTS));
161
+        }
162
+
163
+        if (Math.abs(mEndDegrees - mStartDegrees) > 0) {
164
+            mSwipeDegrees = mEndDegrees - mStartDegrees;
165
+        }
166
+    }
167
+
168
+    @Override
169
+    protected void setAlpha(int alpha) {
170
+        mPaint.setAlpha(alpha);
171
+    }
172
+
173
+    @Override
174
+    protected void setColorFilter(ColorFilter cf) {
175
+        mPaint.setColorFilter(cf);
176
+    }
177
+
178
+    @Override
179
+    protected void reset() {
180
+        resetOriginals();
181
+    }
182
+
183
+    private void initStrokeInset(float width, float height) {
184
+        float minSize = Math.min(width, height);
185
+        float strokeInset = minSize / 2.0f - mCenterRadius;
186
+        float minStrokeInset = (float) Math.ceil(mStrokeWidth / 2.0f);
187
+        mStrokeInset = strokeInset < minStrokeInset ? minStrokeInset : strokeInset;
188
+    }
189
+
190
+    private void storeOriginals() {
191
+        mOriginEndDegrees = mEndDegrees;
192
+        mOriginStartDegrees = mEndDegrees;
193
+    }
194
+
195
+    private void resetOriginals() {
196
+        mOriginEndDegrees = 0;
197
+        mOriginStartDegrees = 0;
198
+
199
+        mEndDegrees = 0;
200
+        mStartDegrees = 0;
201
+
202
+        mSwipeDegrees = 1;
203
+    }
204
+
205
+    private void apply(Builder builder) {
206
+        this.mWidth = builder.mWidth > 0 ? builder.mWidth : this.mWidth;
207
+        this.mHeight = builder.mHeight > 0 ? builder.mHeight : this.mHeight;
208
+        this.mStrokeWidth = builder.mStrokeWidth > 0 ? builder.mStrokeWidth : this.mStrokeWidth;
209
+        this.mCenterRadius = builder.mCenterRadius > 0 ? builder.mCenterRadius : this.mCenterRadius;
210
+
211
+        this.mDuration = builder.mDuration > 0 ? builder.mDuration : this.mDuration;
212
+
213
+        this.mColor = builder.mColor != 0 ? builder.mColor : this.mColor;
214
+
215
+        this.mGearCount = builder.mGearCount > 0 ? builder.mGearCount : this.mGearCount;
216
+        this.mGearSwipeDegrees = builder.mGearSwipeDegrees > 0 ? builder.mGearSwipeDegrees : this.mGearSwipeDegrees;
217
+
218
+        setupPaint();
219
+        initStrokeInset(this.mWidth, this.mHeight);
220
+    }
221
+
222
+    public static class Builder {
223
+        private Context mContext;
224
+
225
+        private int mWidth;
226
+        private int mHeight;
227
+        private int mStrokeWidth;
228
+        private int mCenterRadius;
229
+
230
+        private int mDuration;
231
+
232
+        private int mColor;
233
+
234
+        private int mGearCount;
235
+        private int mGearSwipeDegrees;
236
+
237
+        public Builder(Context mContext) {
238
+            this.mContext = mContext;
239
+        }
240
+
241
+        public Builder setWidth(int width) {
242
+            this.mWidth = width;
243
+            return this;
244
+        }
245
+
246
+        public Builder setHeight(int height) {
247
+            this.mHeight = height;
248
+            return this;
249
+        }
250
+
251
+        public Builder setStrokeWidth(int strokeWidth) {
252
+            this.mStrokeWidth = strokeWidth;
253
+            return this;
254
+        }
255
+
256
+        public Builder setCenterRadius(int centerRadius) {
257
+            this.mCenterRadius = centerRadius;
258
+            return this;
259
+        }
260
+
261
+        public Builder setDuration(int duration) {
262
+            this.mDuration = duration;
263
+            return this;
264
+        }
265
+
266
+        public Builder setColor(int color) {
267
+            this.mColor = color;
268
+            return this;
269
+        }
270
+
271
+        public Builder setGearCount(int gearCount) {
272
+            this.mGearCount = gearCount;
273
+            return this;
274
+        }
275
+
276
+        public Builder setGearSwipeDegrees(@IntRange(from = 0, to = 360) int gearSwipeDegrees) {
277
+            this.mGearSwipeDegrees = gearSwipeDegrees;
278
+            return this;
279
+        }
280
+
281
+        public GearLoadingRenderer build() {
282
+            GearLoadingRenderer loadingRenderer = new GearLoadingRenderer(mContext);
283
+            loadingRenderer.apply(this);
284
+            return loadingRenderer;
285
+        }
286
+    }
287
+}

+ 27 - 0
views/src/main/res/values/attrs.xml

@@ -121,4 +121,31 @@
121 121
         <attr name="hasStickyHeaders" format="boolean" />
122 122
         <attr name="isDrawingListUnderStickyHeader" format="boolean" />
123 123
     </declare-styleable>
124
+
125
+    <declare-styleable name="LoadingView">
126
+        <attr name="loading_renderer">
127
+            <!--circle rotate-->
128
+            <enum name="MaterialLoadingRenderer" value="0"/>
129
+            <enum name="LevelLoadingRenderer" value="1"/>
130
+            <enum name="WhorlLoadingRenderer" value="2"/>
131
+            <enum name="GearLoadingRenderer" value="3"/>
132
+            <!--circle jump-->
133
+            <enum name="SwapLoadingRenderer" value="4"/>
134
+            <enum name="GuardLoadingRenderer" value="5"/>
135
+            <enum name="DanceLoadingRenderer" value="6"/>
136
+            <enum name="CollisionLoadingRenderer" value="7"/>
137
+            <!--Scenery-->
138
+            <enum name="DayNightLoadingRenderer" value="8"/>
139
+            <enum name="ElectricFanLoadingRenderer" value="9"/>
140
+            <!--Animal-->
141
+            <enum name="FishLoadingRenderer" value="10"/>
142
+            <enum name="GhostsEyeLoadingRenderer" value="11"/>
143
+            <!--Goods-->
144
+            <enum name="BalloonLoadingRenderer" value="12"/>
145
+            <enum name="WaterBottleLoadingRenderer" value="13"/>
146
+            <!--ShapeChange-->
147
+            <enum name="CircleBroodLoadingRenderer" value="14"/>
148
+            <enum name="CoolWaitLoadingRenderer" value="15"/>
149
+        </attr>
150
+    </declare-styleable>
124 151
 </resources>